Skip to content

fix(runtime): honour allowReserved on path parameters - #112

Merged
tanmaykm merged 2 commits into
mainfrom
tan/path-allow-reserved
Sep 11, 2026
Merged

tanmaykm merged 2 commits into
mainfrom
tan/path-allow-reserved

Conversation

@tanmaykm

@tanmaykm tanmaykm commented Sep 11, 2026

Copy link
Copy Markdown
Member

Problem

The runtime percent-encodes every reserved character in a path parameter value, including /, regardless of the parameter's allowReserved flag. The planner already records allow_reserved on the operation descriptor for path parameters (allow_reserved = true appears in the emitted _OP_* tables), but _path_parameter never looked at it.

OAS 3.2 lists allowReserved under the path-parameter branch of the Parameter Object (styles-for-path in schemas/oas-3.2.json), so honouring it there is conformant rather than an extension: reserved characters go on the wire as-is. It matters for documents whose path parameter is itself a slash-delimited path — Open Policy Agent's /v1/data/{path} is the motivating case (path is some/document/path, and the openapi-generator client needed escape_path_params=false for it). Without the flag the server sees a single %2F-joined segment and returns an empty result.

Version caveat

The three bundled spec schemas disagree about whether the field is legal on a path parameter. Verified by generating a client and a server from a document declaring it, under the default strict = true:

Document version allowReserved on a path parameter
3.0 accepted (generic Parameter property; PathParameter does not forbid it)
3.1 rejected — scoped to styles-for-query under unevaluatedProperties: false, so the document fails to load at all, even with strict = false
3.2 accepted, explicitly

This matters most for the MIGRATION.md row, which targets people coming off 0.2.x escape_path_params = false and who are likely to hold a 3.1 spec: they hit a load error rather than the feature.

Change

  • _path_scalar / _path_array / _path_object / _path_parameter take allow_reserved and pass it to _escape, which already implements the reserved-set pass-through for query parameters. _append_parameter! forwards descriptor.allow_reserved on both path branches (plain and content-serialized).
  • Everything else is still percent-encoded ("opa/examples/public servers"opa/examples/public%20servers); the default (allowReserved absent or false) is unchanged.
  • Unit tests for simple/label/matrix styles and an HTTP integration test asserting the request target.
  • docs/src/clients.md documents the behaviour and the version caveat; MIGRATION.md gains a row for the 0.2.x escape_path_params = false option, noting the 3.0/3.2 requirement.
  • Version 1.1.1. No generated code changes: the descriptor already carried the flag, so existing generated clients pick the fix up on upgrade.

Known gap: servers (#113)

This is a client-side fix. A generated server built from the same document cannot route the value the client now sends: register! mounts the path template as written and HTTP.Router matches {name} against a single segment, so the request 404s before reaching _decode_path_parameter.

/documents/opa%2Fexamples%2Fpublic%20servers  -> 200   (previous wire form)
/documents/opa/examples/public%20servers      -> 404   (what the client now sends)

Client and server generated from one document therefore cannot talk to each other for such a parameter. _decode_path_parameter is not at fault — given the full multi-segment text it already decodes correctly; only the route shape is wrong. The fix is constrained (HTTP.jl's ** must be the last path segment, and contributes nothing to getparams), so it is tracked separately in #113 rather than bundled here. docs/src/servers.md records the limitation in the meantime.

Full test suite passes locally on Julia 1.12.

OAS 3.2 lists allowReserved under the path-parameter branch of the Parameter
Object (styles-for-path in schemas/oas-3.2.json), so honouring it there is
conformant rather than an extension: reserved characters go on the wire as-is.
It matters for documents whose path parameters are themselves slash-delimited
paths (OPA data documents, proxied object paths). Until now _path_parameter
ignored the flag, so every '/' became %2F and the server saw a single segment;
the planner already recorded allow_reserved on the descriptor, so no
regeneration is needed.

Version caveat: 3.0 tolerates the field on a path parameter and 3.2 blesses it,
but 3.1 scopes it to query parameters under unevaluatedProperties:false, so a
3.1 document declaring it fails to load at all, even with strict = false.

Thread allow_reserved through _path_scalar/_path_array/_path_object and the
parameter-content branch, keep percent-encoding everything else, and cover it
with unit and HTTP integration tests. Document the behaviour and the 0.2.x
escape_path_params migration path. Bump to 1.1.1.
The src/runtime.jl comment introduced with the path-parameter allowReserved fix
framed the behaviour as a pragmatic deviation from the spec. It is not: OAS 3.2
lists allowReserved under the path-parameter branch of the Parameter Object
(styles-for-path in schemas/oas-3.2.json). Rewrite the comment to cite the
schema file rather than assert a deviation.

The real constraint is a version caveat, verified against all three bundled
schemas by generating a client and a server from a document declaring
allowReserved on a path parameter, under the default strict = true:

  3.0 -> accepted (generic Parameter property; PathParameter does not forbid it)
  3.1 -> rejected; scoped to styles-for-query under unevaluatedProperties:false,
         so the document fails to load at all, even with strict = false
  3.2 -> accepted, explicitly

That matters most for the MIGRATION.md row, which targets people coming off
0.2.x escape_path_params = false and who are likely to hold a 3.1 spec; they
would hit a load error rather than the feature. Add the caveat there and in
docs/src/clients.md.

Also record in docs/src/servers.md that generated servers cannot yet route such
a value: they register the path template as written and HTTP.Router matches
{name} against a single segment, so a request carrying an unescaped '/' 404s
before reaching the handler. Client and server generated from one document
therefore cannot talk to each other for that parameter. Tracked in #113.

Comment and docs only; no behaviour change.
@tanmaykm
tanmaykm force-pushed the tan/path-allow-reserved branch from d00f169 to f9d6eb7 Compare September 11, 2026 06:37
@tanmaykm
tanmaykm merged commit 62818a5 into main Sep 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant